
package controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JPanel;

import modelInterfaces.AbstractModelFactory;
import modelInterfaces.IEvent;
import modelInterfaces.IPrediction;
import modelInterfaces.IResult;
import modelInterfaces.ISubject;
import modelInterfaces.ITest;
import pluginInterfaces.IMetric;
import pluginInterfaces.IPluginClassificationMetric;
import pluginInterfaces.IPluginErrorMetric;
import pluginInterfaces.IRecommender;
import utils.JFileChooserClass;
import view.DialogSetupRunTest;
import view.DialogSetupTestGraphic;
import viewInterfaces.AbstractViewFactory;
import viewInterfaces.IViewBenchmarkPanel;
import controllerInterfaces.AbstractControllerFactory;
import controllerInterfaces.IControllerBenchmarkPanel;
import controllerInterfaces.IControllerLoadTestResult;
import controllerInterfaces.IControllerMain;
import controllerInterfaces.IControllerOutputJarSetup;
import controllerInterfaces.IControllerSaveTestResult;
import controllerInterfaces.IControllerTestResultGraphic;
import dao.DAOException;
import dao.DAOFactory;

/**
 * Controller class of benchmark panel. Contains all the options to run tests of recommendation 
 * algorithms and generate the component and graph the results
 * 
 * @author Andy Cullache
 */
public class ControllerBenchmarkPanel implements IControllerBenchmarkPanel{

    private IViewBenchmarkPanel benchmarkPanel;
    private IControllerMain myController;
    private List<ITest> loadTest;
    private IControllerSaveTestResult controllerSaveTest;
    private IControllerLoadTestResult controllerLoadTest;
    private IControllerOutputJarSetup controllerOutput;
    private IControllerTestResultGraphic controllerTestResultGraphic;
    private DialogSetupRunTest dialogRunTest;
    private DialogSetupTestGraphic dialogTestGraphic;

    /**
	 * Default constructor.
	 */
    public ControllerBenchmarkPanel(){
    	loadTest = new ArrayList<ITest>();
        benchmarkPanel = AbstractViewFactory.getAbstractViewFactory().getBenchmarkPanel();
        benchmarkPanel.setController(this);
    }

	/**
	* Returns the list of available tests.
	* 
	* @return      list of available tests.
	*/
    public List<ITest> getTests(){
    	    return this.myController.getTestList();
    }
    
    /**
     * Run a test which is given as a parameter for a number of total events and hidden from a particular user. 
     * Runs a number of times the test depending on the maximum number of recommendations (depends on the parameter increment).
     * 
     * @param test      contains algorithms and metrics to run.
     * @param hiddenEvents      events from training set for user.
     * @param allEvents      total events from dataset.
     * @param subject      user to provide recommendations.
     * @param maxRecommended      maximum number of recommendations (limit to iterate tests).
     * @param increment      increment value in each iteration of the test.
     */
    public void runTest(ITest test, List<IEvent> hiddenEvents, List<IEvent> allEvents,
    		ISubject subject,  int maxRecommended, int increment){
    	
        List<IPrediction> recommendations = null;
        List<IResult> resultM;
        double aux;
           	
        for(IRecommender setupAlg : test.getAlgorithms()){
            resultM = new ArrayList<IResult>();	
            for(int i = increment; i<=maxRecommended; i=i+increment){
            	recommendations = ((IRecommender) setupAlg).getAlgorithm().recommend(subject, allEvents, i);            
	            for(IMetric setupMetric : test.getMetrics()){
	            	if(setupMetric.getMetric().getClass().getSuperclass().equals(IPluginClassificationMetric.class)){
	            		IPluginClassificationMetric metPlug = (IPluginClassificationMetric) setupMetric.getMetric();    
		                IResult resMet = AbstractModelFactory.getModelFactory().getResultMetric();
		                aux = metPlug.accuracy(hiddenEvents, recommendations);
		                resMet.setMetric(setupMetric);
		                resMet.setResultAverage(aux);
		                resMet.setSubject(subject);
		                resMet.setVariable(i);
		                resultM.add(resMet);
	            	}
	    			if(setupMetric.getMetric().getClass().getSuperclass().equals(IPluginErrorMetric.class)){
	    				IPluginErrorMetric metPlug = (IPluginErrorMetric) setupMetric.getMetric();    
		                IResult resMet = AbstractModelFactory.getModelFactory().getResultMetric();
		                aux = metPlug.computeError(hiddenEvents, recommendations);
		                resMet.setMetric(setupMetric);
		                resMet.setResultAverage(aux);
		                resMet.setSubject(subject);
		                resMet.setVariable(i);
		                resultM.add(resMet);
	    			}
	            	
	            }
            }
            if(test.getResultMap().containsKey(setupAlg.getName())){
            	resultM.addAll(test.getResultMap().get(setupAlg.getName()));
            }
            test.getResultMap().put(setupAlg.getName(), resultM);
        }
    }
    
    /**
     * run Tests list selected from the view of this controller.
     * 
     * @param numberUsers      number of users for testing.
     * @param maxRecommended      maximum number of recommendations (limit to iterate tests).
     * @param increment      increment value in each iteration of the test.      
     */
	public void runTestList(int numberUsers, int percent, int maxRecommended, int increment){
        List<ISubject> allSubject;
        List<ISubject> testSubjects;
        try {
        	for(ITest test : this.benchmarkPanel.getSelectedTest()){
                if(test.getAlgorithms().size() == 0 || test.getMetrics().size() == 0){
                }
                else{
	        		test.getResultMap().clear();
					allSubject = DAOFactory.getDAOFactory().getUserDAO().retrieveAllFromType(test.getEventType());
					testSubjects = getTestSubject(allSubject, numberUsers);
					for(ISubject subject : testSubjects){
				    	List<IEvent> allEvents = new ArrayList<>();
				    	allEvents.addAll(DAOFactory.getDAOFactory().getEventDAO().retrieveAllFromType(test.getEventType()));
						List<IEvent> subjectEvents = new ArrayList<>(); 
						subjectEvents.addAll(DAOFactory.getDAOFactory().getEventDAO().retrieveAllFromType(subject, test.getEventType()));
						int aux = getHiddenEventsNumber(subjectEvents,percent);
						List<IEvent> hiddenEvents = this.removeEvents(allEvents, subjectEvents,aux);
						runTest(test, hiddenEvents, allEvents, subject, maxRecommended, increment);
					}
                }
        	}
		} catch (DAOException e) {
				e.printStackTrace();
		}
    }
	
	/**
	 * Calculates the number of events to hide a user according to the percentage that goes from parameter.
	 * 
	 * @param alleventsSubject      a set of user events.
	 * @param percent     percentage of training data.
	 * @return      number of events to hide.
	 */
	private int getHiddenEventsNumber(List<IEvent> alleventsSubject, double percent){
		double decimal = percent/100; 
		int total = alleventsSubject.size();
		double aux = decimal*total;
		if(aux<1)
			aux++;
		return (int)aux;
	}

	/**
	 * Returns a set of users for use in testing, select a number of users indicated as a parameter
	 *  within a set of users who are also parameters.
	 *  
	 * @param allSubject      set of users.
	 * @param numberUsers      numbers of test users.
	 * @return      users for tests.
	 */
    private List<ISubject> getTestSubject(List<ISubject> allSubject, int numberUsers) {
    	List<ISubject> testSubjects = new ArrayList<ISubject>();
		Random ran = new Random();
		int aux = 0;
    	if(allSubject.size() <= numberUsers){
    		testSubjects.addAll(allSubject);
    	}
    	else{
    		while(testSubjects.size()<numberUsers){
    			aux = ran.nextInt(allSubject.size());
    			if(!testSubjects.contains((allSubject.get(aux)))){
    				testSubjects.add(allSubject.get(aux));
    			}
    		}
    	}
    	return testSubjects;
	}

	/**
	 * Returns the JPanel that represents the view of this controller.
	 * 
	 * @return      panel the view of this controller.
	 */
	@Override
	public JPanel getPanel() {
		return this.benchmarkPanel.GetPanel();
	}

	/**
	* Sets the main controller as controller of this class.
	* 
	* @param controllerMain      main controller class.
	*/
	@Override
	public void setMainController(IControllerMain myController) {
		this.myController = myController;		
	}

	/**
	* Establishing the list of tests from the project to this controller.
	* 
	* @param      tests list from current project.
	*/
	@Override
	public void setTests(List<ITest> tests) {
		this.myController.setTestList(tests);		
	}
	
	/**
	 * Create a view where you select the parameters of the result file to save.
	 */
	public void ChooseDirectoryToSaveTest(){
        this.controllerSaveTest = AbstractControllerFactory.getAbstractControllerFactory().getControllerSaveTestResult();
        this.controllerSaveTest.setTests(getTests());
	}
	
	/**
	 * Create a view where you select the result file to load.
	 */
	public void ChooseDirectoryToLoadTest(){
        this.controllerLoadTest = AbstractControllerFactory.getAbstractControllerFactory().getControllerLoadTestResult();
        this.controllerLoadTest.setController(this);
	}
	
	/**
	 * Set a values to result of table in benchmark panel.
	 */
	public void setTable(){
		this.benchmarkPanel.setTable();
	}
	
	/**
	 * Hides a certain number of events (given by the parameter split) of the total events (events are hidden from a user).
	 *  Will return events that are hidden.
	 * 
	 * @param allEvents      all events from dataset.
	 * @param subjectEvents      events from a subject.
	 * @param split      number of events to hide. 
	 * @return      hidden events.
	 */
	private List<IEvent> removeEvents(List<IEvent> allEvents, List<IEvent> subjectEvents, int split){
		List<IEvent> auxList = subjectEvents.subList(split,subjectEvents.size());
		List<IEvent> all = new ArrayList<IEvent>();
		all.addAll(allEvents);
		for(IEvent event : all){
			if(auxList.contains(event))
				allEvents.remove(event);
		}
		return auxList;
	}
	
	/**
	 * Displays a dialog which sets the parameters for test execution.
	 */
	public void showDialogRunTest(){
		this.dialogRunTest = new DialogSetupRunTest(this);
	}
	
	/**
	 * Display a dialog which sets parameters for generates plot.
	 */
	public void showDialogTestGraphic(){
		this.dialogTestGraphic = new DialogSetupTestGraphic(this);
	}
	
	/**
	* Returns the list of configurations of algorithms present in the current project.
	* 
	* @return      configuration list of algorithms.
	*/
	public List<IRecommender> getRecommenderList(){
		return this.myController.getRecommenderList();
	}

	/**
	 * Create a output controller to generate jar component.
	 */
	@Override
	public void showOutputSetupView() {
		this.controllerOutput = AbstractControllerFactory.getAbstractControllerFactory().getControllerOutputJar();
		this.controllerOutput.setController(this);
	}
	
	/**
	 * Create a graphic controller to generate plot with result tests.
	 */
	@Override
	public void showGraphicView() {
		this.controllerTestResultGraphic = AbstractControllerFactory.getAbstractControllerFactory().getControllerGraphic();
	}

	/**
	 * Return a test list from this controller.
	 * 
	 * @return      test list.
	 */
	public List<ITest> getLoadTest() {
		return loadTest;
	}
	
	/**
	* Opens a view to set the directory and name of file to read.
	* 
	* @return      a set of "String" size 2, contains the name and path of the file to read.
	*/
	public String[] selectDirectory(){
		return JFileChooserClass.selectFileToRead();
	}

	/**
	 * Set test to plot (containing test results).
	 * 
	 * @param test      test containing the results.
	 * @return      true if test contains result.
	 */
	@Override
	public boolean setTestToGraphic(ITest test) {
		if(test.getResultMap()==null || test.getResultMap().isEmpty()){
			return false;
		}
		else{
			this.controllerTestResultGraphic.generateGraphics(test);
			return true;
		}
	}

	/**
	 * Sets the directory and name of the CSV file containing the results to be 
	 * graphed (complies the same function as the method setTestToGraphic ()).
	 * 
	 * @param dir      PATH of CSV file.
	 * @param filename      file name of CSV file.
	 * @return      true if test result can be read.
	 */
	@Override
	public boolean setTestResultToGraphic(String dir, String fileName) {
		return this.controllerTestResultGraphic.generateGraphics(dir, fileName);
	}
	
	/**
	 * Clean and empty lists of test results.
	 */
	public void clearResultTest(){
		getLoadTest().clear();
		for(ITest test : getTests()){
			test.getResultMap().clear();
		}
	}
	
	/**
	* Loading the list of tests in the view previously created in Test panel
	* 
	*/
	public void loadTestsToView()
	{
		this.benchmarkPanel.setTestSetup();
		this.benchmarkPanel.setTable();
	}

}
